2024_stockhausen_kreuzspiel.py

#

SPDX-FileCopyrightText: 2025 Maëlle Durin & Margot Schuster SPDX-FileCopyrightText: 2025 AlICe laboratory https://alicelab.be

SPDX-License-Identifier: GPL-3.0-or-later

import bpy
import random
#

GRILLE Nettoyer la scène

bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.delete(use_global=False, confirm=False)
bpy.ops.outliner.orphans_purge()
#

Mappe la valeur ‘value’ de l’intervalle [min_val, max_val] vers l’intervalle [target_min, target_max].

def mapper(value, min_val, max_val, target_min, target_max):
#
    return target_min + ((value - min_val) / (max_val - min_val)) * (
        target_max - target_min
    )
#

Fonction pour créer un trait rectangulaire

def creer_trait(x, y, largeur_x, largeur_y, hauteur=10):
#

Crée un trait rectangulaire positionné à (x, y) avec la largeur et la hauteur spécifiées.

    bpy.ops.mesh.primitive_cube_add(
        size=1, location=(x, y, hauteur / 2)
    )  # Centré à mi-hauteur
    obj = bpy.context.object
    obj.scale = (
        largeur_x / 1,
        largeur_y / 1,
        hauteur / 1,
    )  # Divisé par 2 car la taille de base du cube est 2x2x2
#

Créer 17 traits le long de l’axe X mappés dans un carré de 10x10

x_position = 1  # Position de départ
espacements_x = [
    random.choice([0.5, 1, 2]) for _ in range(16)
]  # 16 espaces pour 17 traits
total_length_x = sum(espacements_x)  # Longueur totale nécessaire
position_x = 1  # Position initiale du premier trait

for i in range(17):  # 17 traits
    x_mapped = mapper(
        position_x, 0, total_length_x, 0, 9
    )  # Mapper la position dans la plage [0, 10]
    creer_trait(
        x_mapped, 5, largeur_x=0.15, largeur_y=10, hauteur=10
    )  # Largeur X = 0.1, Largeur Y = 10, Hauteur = 10
    if i < len(espacements_x):  # Éviter un index hors limi0tes
        position_x += espacements_x[i]  # Avancer la position de l'espacement aléatoire
#

Créer 9 traits le long de l’axe Y mappés dans un carré de 10x10

y_position = 1  # Position de départ
espacements_y = [
    random.choice([0.5, 1, 2]) for _ in range(14)
]  # 8 espaces de 1 pour 9 traits
total_length_y = sum(espacements_y)  # Longueur totale nécessaire
position_y = 1  # Position initiale du premier trait

for i in range(14):  # 9 traits
    y_mapped = mapper(
        position_y, 0, total_length_y, 0, 9.5
    )  # Mapper la position dans la plage [0, 10]
    creer_trait(
        5, y_mapped, largeur_x=10, largeur_y=0.15, hauteur=10
    )  # Largeur X = 10, Largeur Y = 0.1, Hauteur = 10
    if i < len(espacements_y):  # Éviter un index hors limites
        position_y += espacements_y[i]  # Avancer la position de l'espacement
#

CURVE

#

Mappers pour X et Z

def mapper_X(value):
    mapping = {
        1: -0.4,
        2: 0.6,
        3: 1.2,
        4: 1.8,
        5: 2.3,
        6: 2.8,
        7: 3.4,
        8: 4,
        9: 4.6,
        10: 5,
        11: 5.625,
        12: 6.25,
        13: 6.875,
        14: 7.5,
        15: 8.125,
        16: 8.75,
        17: 9.375,
        18: 10.2,
    }
    return mapping.get(value, value)
#
def mapper_X1(value):
    mapping = {
        1: -0.4,
        2: 0.7,
        3: 1.4,
        4: 2.1,
        5: 2.8,
        6: 3.6,
        7: 5,
        8: 5.73,
        9: 6.4222,
        10: 7.12222,
        11: 7.8999,
        12: 8.5999,
        13: 9.29,
        14: 10.2,
    }
    return mapping.get(value, value)
#
def mapper_Z(value):
    mapping = {1: 0, 2: 1.43, 3: 2.8, 4: 4.3, 5: 5.1, 6: 6.5, 7: 7.2, 8: 8.5}
    return mapping.get(value, value)
#

Fonction pour interpoler des points pour correspondre au nombre de points

def interpolate_points(points, target_count):
    interpolated = []
    step = (len(points) - 1) / (target_count - 1)
    for i in range(target_count):
        idx = i * step
        lower = int(idx)
        upper = min(lower + 1, len(points) - 1)
        t = idx - lower
#

Interpolation linéaire

        x = (1 - t) * points[lower][0] + t * points[upper][0]
        y = (1 - t) * points[lower][1] + t * points[upper][1]
        z = (1 - t) * points[lower][2] + t * points[upper][2]
        interpolated.append((x, y, z))
    return interpolated
#

Fonction pour créer une courbe Bézier à partir de points

def create_bezier_curve(name, points):
    curve_data = bpy.data.curves.new(name=name, type="CURVE")
    curve_data.dimensions = "3D"
    spline = curve_data.splines.new(type="BEZIER")
    spline.bezier_points.add(len(points) - 1)

    for i, (x, y, z) in enumerate(points):
        bezier_point = spline.bezier_points[i]
        bezier_point.co = (x, y, z)
        bezier_point.handle_left_type = "AUTO"
        bezier_point.handle_right_type = "AUTO"

    curve_obj = bpy.data.objects.new(name, curve_data)
    bpy.context.collection.objects.link(curve_obj)
    return curve_obj
#

Fonction pour convertir la courbe en maillage

def convert_curve_to_mesh(curve_obj):
    bpy.context.view_layer.objects.active = curve_obj
    curve_obj.select_set(True)
    bpy.ops.object.convert(target="MESH")
    return curve_obj
#

Générer les points de la première courbe

points_curve1 = [
    (mapper_X(i + 1), -0.3, mapper_Z(random.choice([3, 4, 6]))) for i in range(18)
]
#

Générer les points de la deuxième courbe

points_curve2 = [
    (mapper_X1(i + 1), 10.4, mapper_Z(random.choice([2, 3, 4, 6]))) for i in range(14)
]
#

Interpoler la courbe qui a le moins de points

n_points1 = len(points_curve1)
n_points2 = len(points_curve2)

if n_points1 > n_points2:
    points_curve2 = interpolate_points(points_curve2, n_points1)
else:
    points_curve1 = interpolate_points(points_curve1, n_points2)
#

Créer les courbes Bézier

curve1 = create_bezier_curve("BezierCurve1", points_curve1)
curve2 = create_bezier_curve("BezierCurve2", points_curve2)
#

Convertir les courbes en maillages

convert_curve_to_mesh(curve1)
convert_curve_to_mesh(curve2)
#

Récupérer les sommets des deux maillages

verts1 = [v.co for v in curve1.data.vertices]
verts2 = [v.co for v in curve2.data.vertices]
#

Créer un nouveau maillage pour la surface connectée

plane_mesh = bpy.data.meshes.new("ConnectingSurface")
plane_object = bpy.data.objects.new("ConnectingSurface", plane_mesh)
bpy.context.collection.objects.link(plane_object)
#

Relier les sommets des deux courbes par des quads

vertices = verts1 + verts2
faces = []
#

Relier les sommets des deux courbes par des quads

n = len(verts1)  # Les deux courbes ont maintenant le même nombre de points
for i in range(n - 1):
    faces.append([i, i + 1, n + i + 1, n + i])
#

Créer le maillage

plane_mesh.from_pydata(vertices, [], faces)
plane_mesh.update()

bpy.ops.object.select_all(action="DESELECT")
bpy.data.objects["ConnectingSurface"].select_set(True)
bpy.ops.transform.translate(
    value=(-0, -0, -1.2),
    orient_type="LOCAL",
    orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
    orient_matrix_type="LOCAL",
    constraint_axis=(False, False, True),
    mirror=False,
    use_proportional_edit=False,
    proportional_edit_falloff="SMOOTH",
    proportional_size=1,
    use_proportional_connected=False,
    use_proportional_projected=False,
    snap=False,
    snap_elements={"INCREMENT"},
    use_snap_project=False,
    snap_target="CLOSEST",
    use_snap_self=True,
    use_snap_edit=True,
    use_snap_nonedit=True,
    use_snap_selectable=False,
)
#

code pour faire de la courbe un volume solide et action booléenne par la suite

bpy.ops.object.select_all(action="DESELECT")
bpy.data.objects["ConnectingSurface"].select_set(True)
bpy.context.view_layer.objects.active = bpy.data.objects["ConnectingSurface"]
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.quads_convert_to_tris(quad_method="BEAUTY", ngon_method="BEAUTY")
bpy.ops.object.editmode_toggle()
bpy.ops.object.duplicate_move(
    OBJECT_OT_duplicate={"linked": False, "mode": "TRANSLATION"},
    TRANSFORM_OT_translate={"value": (0, 0, 0)},
)
bpy.ops.object.editmode_toggle()

bpy.ops.mesh.extrude_region_move(
    MESH_OT_extrude_region={
        "use_normal_flip": False,
        "use_dissolve_ortho_edges": False,
        "mirror": False,
    },
    TRANSFORM_OT_translate={
        "value": (0, 0, 3.8),
        "orient_type": "LOCAL",
        "orient_matrix": ((1, 0, 0), (0, 1, 0), (0, 0, 1)),
        "orient_matrix_type": "LOCAL",
        "constraint_axis": (False, False, True),
        "mirror": False,
        "use_proportional_edit": False,
        "proportional_edit_falloff": "SMOOTH",
        "proportional_size": 1,
        "use_proportional_connected": False,
        "use_proportional_projected": False,
        "snap": False,
        "snap_elements": {"INCREMENT"},
        "use_snap_project": False,
        "snap_target": "CLOSEST",
        "use_snap_self": True,
        "use_snap_edit": True,
        "use_snap_nonedit": True,
        "use_snap_selectable": False,
        "snap_point": (0, 0, 0),
        "snap_align": False,
        "snap_normal": (0, 0, 0),
        "gpencil_strokes": False,
        "cursor_transform": False,
        "texture_space": False,
        "remove_on_cancel": False,
        "use_duplicated_keyframes": False,
        "view2d_edge_pan": False,
        "release_confirm": False,
        "use_accurate": False,
        "use_automerge_and_split": False,
    },
)
bpy.ops.object.editmode_toggle()

bpy.ops.object.select_all(action="DESELECT")


for obj in bpy.data.objects:
    if "cube" in obj.name:
        obj.select_set(True)
        bpy.context.view_layer.objects.active = obj


for obj in bpy.data.objects:
    if "Cube" in obj.name:
        obj.select_set(True)
        bpy.context.view_layer.objects.active = obj
        bpy.ops.object.modifier_add(type="BOOLEAN")
        bpy.context.object.modifiers["Boolean"].object = bpy.data.objects[
            "ConnectingSurface.001"
        ]
        bpy.context.object.modifiers["Boolean"].solver = "FAST"
        bpy.ops.object.modifier_apply(modifier="Boolean")
        obj.select_set(False)

bpy.data.objects["ConnectingSurface.001"].select_set(True)
bpy.ops.object.delete(use_global=False)
bpy.data.objects["ConnectingSurface"].select_set(True)
bpy.ops.object.delete(use_global=False)
bpy.data.objects["BezierCurve1"].select_set(True)
bpy.ops.object.delete(use_global=False)
bpy.data.objects["BezierCurve2"].select_set(True)
bpy.ops.object.delete(use_global=False)

bpy.ops.object.select_all(action="DESELECT")

bpy.ops.object.select_all(action="SELECT")
bpy.ops.object.join()
bpy.context.object.name = "GRILLE"

bpy.ops.object.select_all(action="DESELECT")
#

Colonne Oboie et clarinette

#
def mapper_X(value):
    mapping = {1: 1.5, 2: 3.16, 3: 4.72, 4: 6.28, 5: 7.73, 6: 9.5}
    return mapping.get(
        value, value
    )  # Retourne la valeur originale si elle n'est pas dans le mappage
#
def mapper_Y(value):
    mapping = {1: 1.25, 2: 2.5, 3: 3.75, 4: 5, 5: 6.25, 6: 9.5}
    return mapping.get(value, value)
#
def mapper_Z(value):
    mapping = {1: 2.5, 2: 5, 3: 6.5, 4: 10}
    return mapping.get(value, value)
#

Fonction pour créer une colonne rectangulaire avec rotation

def creer_colonne(x, y, z, largeur):
#

Mapper les coordonnées

    x_mapped = mapper_X(x)
    y_mapped = mapper_Y(y)
    z_mapped = mapper_Z(z)
#

Créer un cube à la position mappée

    bpy.ops.mesh.primitive_cube_add(
        size=1, location=(x_mapped, y_mapped, 10 - z_mapped / 2)
    )  # Divisé par 2 pour centrer la base
    obj = bpy.context.object
    obj.scale = (
        largeur / 2.8,
        largeur / 2.8,
        z_mapped / 1,
    )  # Ajuster la largeur et la hauteur
#

Fonction pour générer des colonnes

def generer_colonnes(x_range, y_values, z_range, largeur, n_colonnes):
    coordinates = set()
    objets = []
    while len(coordinates) < n_colonnes:
        x = random.choice(x_range)
        y = random.choice(y_values)
        z = random.choice(z_range)
        coordinates.add((x, y, z))
    for coord in coordinates:
        creer_colonne(*coord, largeur)
        objets.append(
            bpy.context.object
        )  # Ajouter chaque colonne à la liste des objets
    return objets
#

Fonction pour joindre les colonnes et leur donner un nom

def joindre_colonnes(objets, nom):
    bpy.ops.object.select_all(action="DESELECT")
    for obj in objets:
        obj.select_set(True)
    bpy.context.view_layer.objects.active = objets[0]
    bpy.ops.object.join()
    bpy.context.object.name = nom
#

Paramètres pour chaque ensemble de colonnes

colonnes_configurations = [
    {
        "x_range": [1, 2, 3, 4, 5, 6],
        "y_values": [2, 3, 4, 6],
        "z_range": [1, 2, 4],
        "largeur": 0.5,
        "n_colonnes": 11,
    },
]
#

Générer les colonnes pour chaque configuration et les joindre

for config in colonnes_configurations:
    objets_colonnes = generer_colonnes(
        x_range=config["x_range"],
        y_values=config["y_values"],
        z_range=config["z_range"],
        largeur=config["largeur"],
        n_colonnes=config["n_colonnes"],
    )
    joindre_colonnes(objets_colonnes, "colonneOboie")
#

Colonne CLARINETTE

#
def mapper_X(value):
    mapping = {1: 1.5, 2: 3.16, 3: 4.72, 4: 6.28, 5: 7.73, 6: 9.5}
    return mapping.get(
        value, value
    )  # Retourne la valeur originale si elle n'est pas dans le mappage
#
def mapper_Y(value):
    mapping = {1: 1.25, 2: 2.5, 3: 3.75, 4: 5, 5: 6.25, 6: 9.5}
    return mapping.get(value, value)
#
def mapper_Z(value):
    mapping = {1: 2.5, 2: 5, 3: 6.5, 4: 10}
    return mapping.get(value, value)
#

Fonction pour créer une colonne rectangulaire avec rotation

def creer_colonne(x, y, z, largeur):
#

Mapper les coordonnées

    x_mapped = mapper_X(x)
    y_mapped = mapper_Y(y)
    z_mapped = mapper_Z(z)
#

Créer un cube à la position mappée

    bpy.ops.mesh.primitive_cube_add(
        size=1, location=(x_mapped, y_mapped, z_mapped / 2)
    )  # Divisé par 2 pour centrer la base
    obj = bpy.context.object
    obj.scale = (
        largeur / 2.8,
        largeur / 2.8,
        z_mapped / 1,
    )  # Ajuster la largeur et la hauteur
#

Fonction pour générer des colonnes

def generer_colonnes(x_range, y_values, z_range, largeur, n_colonnes):
    coordinates = set()
    objets = []
    while len(coordinates) < n_colonnes:
        x = random.choice(x_range)
        y = random.choice(y_values)
        z = random.choice(z_range)
        coordinates.add((x, y, z))
    for coord in coordinates:
        creer_colonne(*coord, largeur)
        objets.append(
            bpy.context.object
        )  # Ajouter chaque colonne à la liste des objets
    return objets
#

Fonction pour joindre les colonnes et leur donner un nom

def joindre_colonnes(objets, nom):
    bpy.ops.object.select_all(action="DESELECT")
    for obj in objets:
        obj.select_set(True)
    bpy.context.view_layer.objects.active = objets[0]
    bpy.ops.object.join()
    bpy.context.object.name = nom
#

Paramètres pour chaque ensemble de colonnes

colonnes_configurations = [
    {
        "x_range": [1, 2, 3, 4, 5, 6],
        "y_values": [1, 2, 3, 4, 5, 6],
        "z_range": [1, 2, 3, 4],
        "largeur": 0.5,
        "n_colonnes": 16,
    },
]
#

Générer les colonnes pour chaque configuration et les joindre

for config in colonnes_configurations:
    objets_colonnes = generer_colonnes(
        x_range=config["x_range"],
        y_values=config["y_values"],
        z_range=config["z_range"],
        largeur=config["largeur"],
        n_colonnes=config["n_colonnes"],
    )
    joindre_colonnes(objets_colonnes, "colonneClarinet")
#

CODE POUR FAIRE LE BOOLEAN DES COLONNES ( chargement très lent )

'''
#DIVIDER
def make_boolean(base_shape, negative_shape):
#DIVIDER
    base_shape = base_shape.name
    negative_shape = negative_shape.name
#DIVIDER

#DIVIDER
    bpy.ops.object.select_all(action="SELECT")
#DIVIDER
    bpy.data.objects[negative_shape].select_set(False)
#DIVIDER
    bpy.context.view_layer.objects.active = bpy.data.objects[base_shape]
#DIVIDER
    bpy.ops.object.modifier_add(type="BOOLEAN")
    bpy.context.object.modifiers["Boolean"].object = bpy.data.objects[negative_shape]
    bpy.context.object.modifiers["Boolean"].use_self = True
    bpy.ops.object.modifier_apply(modifier="Boolean")
#DIVIDER
    bpy.ops.object.select_all(action="DESELECT")
#DIVIDER




make_boolean(bpy.data.objects["GRILLE"], bpy.data.objects["colonneOboie"])
make_boolean(bpy.data.objects["GRILLE"], bpy.data.objects["colonneClarinet"])

'''

#

Creates a Boolean operation between two given objects.

Parameters: base_shape (object): The object to be subtracted from (base). negative_shape (object): The object used to subtract from (negative).

Returns: None

Notes: This function utilizes Blender’s Boolean modifier to create a boolean difference between two objects.

#
#

Create the cube for boolean

#

Select All

#

Deselect Cube

#

Plane active objects

#

Boolean action with to differentiate the Cube with the Plane

#

Select All

#

Deselect Cube bpy.data.objects[negative_shape].select_set(True) bpy.ops.object.delete(use_global=False)